查看原文
其他

观察者模式

田维常 Java后端技术全栈 2022-09-11

关注公众号“Java后端技术全栈

回复“000”获取程序员必备电子书

大家好,我是老田,今天我给大家分享设计模式中的观察者模式。用贴切的生活故事,以及真实项目场景来讲设计模式,最后用一句话来总结这个设计模式。

故事

昨晚上,睡觉之前,我看了一段《兵法》,在第二十四卷中,看到这么一句:敌不动,我不动

看完这个后,我忽然想起一个设计模式:观察者模式

老田是个喜欢学习春秋战国时期的历史和人文故事,有通道之人,可以私聊!

为什么会想到哦观察者模式呢?请听老田慢慢道来。

本文目录:

关于设计模式系列,前面我们已经分享过9种设计模式:

 三国演义:责任链模式

韩信拜将:委派模式

3年工作必备  装饰器模式

工作五年了,居然还不懂  门面模式

点外卖,让我想起了 策略模式

初级必备:单例模式的7个问题

快速掌握 模板方法 模式

五分钟 掌握 原型模式 

泡图书馆,我想到了 享元模式

言归正传,我们先来看看观察者模式的定义。

定义

观察者模式(Observer Pattern)又叫作发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependent)模式。

定义一种一对多的依赖关系,一个主题对象可被多个观察者对象同时监听,使得每当主题对象状态变化时,所有依赖它的对象都会得到通知并被自动更新,属于行为型设计模式

英文定义:

Defines a one-to-many dependency relationship betweenobjects so that each time an object's state changes,its dependentobjects are notified and automatically updated.

观察者模式的核心是将观察者与被观察者解耦,以类似消息/广播发送的机制联动两者,使被观察者的变动能通知到感兴趣的观察者们,从而做出相应的响应。

通用代码实现

观察者进行抽象:

//抽象观察者
public interface Observer {
    //反应
    void response();
}

两个观察者:

//观察者1
public class ConcreteObserver1 implements Observer {
    @Override
    public void response() {
        System.out.println("具体观察者1作出反应!");
    }
}
//观察者2
public class ConcreteObserver2 implements Observer {
    @Override
    public void response() {
        System.out.println("具体观察者2作出反应!");
    }
}

被观察者(目标)进行抽象:

import java.util.ArrayList;
import java.util.List;

//抽象目标
public abstract class Subject {
    protected List<Observer> observers = new ArrayList<Observer>();
    //增加观察者方法
    public void add(Observer observer) {
        observers.add(observer);
    }
    //删除观察者方法
    public void remove(Observer observer) {
        observers.remove(observer);
    }
    public abstract void notifyObserver()//通知观察者方法
}

具体被观察者:

public class ConcreteSubject extends Subject {
    @Override
    public void notifyObserver() {
        System.out.println("具体目标发生改变...");
        System.out.println("--------------");
        for (Object obs : observers) {
            ((Observer) obs).response();
        }
    }
}

测试类:

public class ObserveTest {
    public static void main(String[] args) {
        Subject subject = new ConcreteSubject();
        Observer obs1 = new ConcreteObserver1();
        Observer obs2 = new ConcreteObserver2();
        subject.add(obs1);
        subject.add(obs2);
        subject.notifyObserver();
    }
}

运行结果:

具体目标发生改变...
--------------
具体观察者1作出反应!
具体观察者2作出反应!

通用代码UMML图


角色

UML图中,我们可以总结出,在观察者模式中有以下四个角色:

  • 抽象主题(Subject):指被观察的对象。该角色是一个抽象类或接口,定义了增加、删除、通知观察者对象的方法。
  • 具体主题(ConcreteSubject):具体被观察者,当其内部状态变化时,会通知已注册的观察者。
  • 抽象观察者(Observer):定义了响应通知的更新方法。
  • 具体观察者(ConcreteObserver1ConcreteObserver1):当得到状态更新的通知时,会自动做出响应。

下面我们来看看一个写生活中的观察者模式的场景。

观察者模式的应用场景

观察者模式在现实生活中的应用也非常广泛,比如:各种APP上的各种消息提示、学校铃声、公众号文章提示、各大网站消息提示等。用图解释




.......

在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。

观察者模式主要适用于以下应用场景。

  • 当一个抽象模型包含两方面内容,其中一方面依赖另一方面。
  • 其他一个或多个对象的变化依赖另一个对象的变化。
  • 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
  • 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

下面我们就用Java代码来实现观察者模式。

案例实现

JDK中的观察者模式

其实在JDK的util包内为我们提供了一套观察者模式的实现,在使用的时候我们只需要继承Observable和Observer类即可,其实观察者模式十分简单,推荐阅读JDK的实现代码真心没有几行。此外在JDK的实现中还增加了一个布尔类型的changed域,通过设置这个变量来确定是否通知观察者。

下面来实现一个微信给所用用户发送"端午安康":

//消息
public class Message {
    private String content;

    public Message(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

平台给用户发消息

import java.util.Observable;
//APP平台
public class App extends Observable {

    private String name;

    public App(String name) {
        this.name = name;
    }

    public void publishMsg(Message message) {
        System.out.println(this.name + " 平台 给 用户们发送消息");
        //setChanged是Observable中的方法
        setChanged();
         //notifyObservers也是Observable中的方法
        notifyObservers(message);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

下面是用户收到消息:

import java.util.Observable;
import java.util.Observer;

public class User implements Observer {

    private String userName;

    public User(String userName) {
        this.userName = userName;
    }
    //实现了Observer的update方法
    @Override
    public void 的update方法(Observable o, Object arg) {
        App app=(App)o;
        Message message=(Message)arg;
        System.out.println(this.userName+" 收到 "+ app.getName()+" 平台 的消息,内容:"+message.getContent());
    }
}

测试类

public class JDKObserverTest {
    public static void main(String[] args) {
        App app = new App("微信");
        Message message = new Message("端午安康!");

        User tian = new User("田哥");
        app.addObserver(tian);

        User zhang = new User("勇哥");
        app.addObserver(zhang);

        User li = new User("苗哥");
        app.addObserver(li);

        User xi = new User("西哥");
        app.addObserver(xi);

        User bing = new User("兵哥");
        app.addObserver(bing);
        
        app.publishMsg(message);
    }
}

运行结果:

微信 平台 给 用户们发送消息
兵哥 收到 微信 平台 的消息,内容:端午安康!
西哥 收到 微信 平台 的消息,内容:端午安康!
苗哥 收到 微信 平台 的消息,内容:端午安康!
勇哥 收到 微信 平台 的消息,内容:端午安康!
田哥 收到 微信 平台 的消息,内容:端午安康!

微信平台给大家发了"端午安康!",对应观察者就能收到"端午安康!"的消息。

在Observable中我们可以对观察者进行添加、删除以及消息通知等操作。


基于Guava API 实现观察者模式

在guava中,也有一套关于观察者模式的,具体实现如下:

添加maven依赖

<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>20.0</version>
 </dependency>

创建Event实现类

public class GuavaEvent {
    //guava包下的注解
    @Subscribe
    public void subscribe(String str) {
        //业务逻辑
        System.out.println("执行 subscribe 方法,入参为 " + str);

    }
}

测试类:

public class GuavaEventTest {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();
        GuavaEvent guavaEven = new GuavaEvent();
        eventBus.register(guavaEven);
        eventBus.post("hello world!");
    }
}

运行结果:

执行 subscribe 方法,入参为 hello world!

小结

客户端只要创建一个EventBus,然后把我们实现的Event注册进去,再把对应的消息放进去,对应我们实现的Event就可以收到客户端发送的消息。

EventBus内部也提供来一系列的方法来供我们方便使用:

  • register 方法作为添加观察者
  • unregister方法删除观察者
  • post 方法发送通知消息等

使用起来非常方便。添加@Subscribe注解就可以创建一个订阅者了,具体的使用方式可以看看官网。

Spring中的观察者模式

在Spring中有一个ApplicationListener,也是采用观察者模式来处理的,ApplicationEventMulticaster作为主题,里面有添加,删除,通知等。

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {

 /**
  * Handle an application event.
  * @param event the event to respond to
  */

 void onApplicationEvent(E event);
}

另外,Spring中的ContextLoaderListener实现了ServletContextListener接口,ServletContextListener接口又继承了EventListener,在JDK中,EventListener有非常广泛的应用。

其实,我们在很多框架中,只要看到XxxListener样式的类,基本上都是观察者模式的实现。

安卓开发中的观察者模式

在Android中我们有一个常用的回调:对于View点击事件的监听。现在我们就来分析一下对于View的监听。

通常在我们使用的时候是这样的:

xxxView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // do something
            }
});

这样我们就注册好了一个回调函数,这也是观察者模式的实现。

观察模式扩展

优缺点

优点

  • 观察者和被观察者是松耦合(抽象耦合)的,符合依赖倒置原则。
  • 分离了表示层(观察者)和数据逻辑层(被观察者),并且建立了一套触发机制,使得数据的变化可以响应到多个表示层上。
  • 实现了一对多的通信机制,支持事件注册机制,支持兴趣分发机制,当被观察者触发事件时,只有感兴趣的观察者可以接收到通知。

缺点

  • 如果观察者数量过多,则事件通知会耗时较长。
  • 事件通知呈线性关系,如果其中一个观察者处理事件卡壳,则会影响后续的观察者接收该事件。
  • 如果观察者和被观察者之间存在循环依赖,则可能造成两者之间的循环调用,导致系统崩溃。

总结

从本文内容,我们很容易看出,观察者模式其实是围绕了解耦的思想来写的,观察者模式作为行为型设计模式,主要也是为了不同的业务行为的代码解耦。最后一句话来总结:

敌不动,我不动

好了,今天的分享就到这里,希望大家能明白什么是观察者模式,也希望大家以后在面试的时候就不要再说你不会设计模式了。

如果有技术探讨、需要学习资源等,请加我微信。

推荐阅读

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存